package org.batatasframework.support.database.sql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.batatasframework.support.database.AliasGenerator;
import org.batatasframework.support.database.SelectedFieldCollection;
import org.batatasframework.support.database.TableInfo;
import org.batatasframework.support.database.jdbc.ConnectionAccessor;
import org.batatasframework.support.database.metadatas.DatabaseMetadataResource;
import org.batatasframework.support.database.metadatas.FieldMetadata;
import org.batatasframework.support.database.metadatas.TableMetadata;
import org.batatasframework.support.database.spi.FieldElementBuilder;
import org.batatasframework.support.database.sql.field.ArraySelectFragment;
import org.batatasframework.support.database.sql.field.ConcatSelectFragment;
import org.batatasframework.support.database.sql.field.DefaultFieldElement;
import org.batatasframework.support.database.sql.field.DefaultSelectFragment;
import org.batatasframework.support.database.sql.field.FieldElement;
import org.batatasframework.support.database.sql.field.FormatSelectFragment;
import org.batatasframework.support.database.sql.field.MultipleSelectAppendable;
import org.batatasframework.support.database.sql.field.SelectFragment;
import org.batatasframework.support.database.sql.sort.SortFragment;
import org.batatasframework.support.database.sql.sort.SortType;
import org.batatasframework.support.database.sql.spi.TableCriterion;
import org.batatasframework.support.database.sql.where.QueryFragment;
import org.batatasframework.support.database.sql.where.SimpleQueryFragment;
import org.batatasframework.support.definition.JoinDefinition;
import org.batatasframework.support.definition.QueryDefinition;
import org.batatasframework.support.definition.TableDefinitionResource;
import org.batatasframework.support.event.HttpRequestAccessor;
import org.springframework.util.Assert;

/**
 * <pre>
 * 定义一个主表，可以是数据库的表，也可以是临时表
 * 
 * 这里是通用的查询语句的生成类，并不是针对特定的功能的，所以应该在数据库公用的包里
 * </pre>
 * 
 * 改为TableInstance
 * 和    JoinInstance
 * @author jackson
 *
 */
public class TableRuntimeMaster extends TableRuntimeBase implements TableMetadata, TableCriterion, SelectedFieldCollection{
	
	protected final Map<TableMetadata, TableRuntimeJoin> joinTables = new LinkedHashMap<>();

	/**
	 * <pre>
	 * 生成的临时表时主表的字段，除了ID，其实的主表的字段都由子类或调用者来指定；
	 * 所有生成后的临时表，直接使用这里的字段返回值而不会再通过临时表对主表或主表
	 * 关联表进行条件过滤；这样做的目的是为了使生成的临时表达到最优化。
	 * 
	 * 在临时表里，这里是需要被生成的临时表的字段，并不一定是需要被查询的字段！
	 * </pre>
	 */
	protected final List<SelectFragment> fields = new ArrayList<>();
	
	/** 这里是实际上生成查询语句的字段 */
	protected final Map<FieldMetadata, FieldElement> columns = new HashMap<>();

	/** 这里主要是用来访问主库里的数据库表结构的定义元数据； */
	protected final ConnectionAccessor databaseMetadataAccessor;
	
	protected final DatabaseMetadataResource databaseMetadataSources;
	
	protected final AliasGenerator aliasGenerator;
	
	/** 创建一个主表，使用自定义的别名 */
	public TableRuntimeMaster(DatabaseMetadataResource databaseMetadataSources, 
			ConnectionAccessor connectionAccessor, 
			TableMetadata tableMetadata,
			AliasGenerator aliasGenerator) throws Exception {
		super(tableMetadata, aliasGenerator.generate());
		
		this.databaseMetadataSources = databaseMetadataSources;
		this.databaseMetadataAccessor = connectionAccessor;
		this.aliasGenerator = aliasGenerator;
	}
	
	public String newAliasName(){
		return aliasGenerator.generate();
	}
	
	public AliasGenerator getAliasGenerator() {
		return aliasGenerator;
	}
	
	@Override
	public Collection<SelectFragment> fields(){
		return fields;
	}

	@Override
	public Collection<FieldMetadata> getFieldMetadatas() {
		List<FieldMetadata> fms = new ArrayList<>();
		for(SelectFragment sf : fields){
			sf.readSource(fms);
		}
		return fms;
	}
	
	public Collection<FieldElement> getColumns(){
		return columns.values();
	}
	
	public Collection<TableRuntimeJoin> getJoins(){
		return joinTables.values();
	}
	
	public int getJoinSize(){
		return joinTables.size();
	}
	
	@Override
	public TableRuntimeJoin getJoin(TableMetadata tableMetadata){
		return joinTables.get(tableMetadata);
	}

	@Override
	public TableRuntimeJoin newJoin(TableDefinitionResource tableDefinition, TableMetadata tableMetadata)throws Exception{
//		JoinTableRuntime joinTableRuntime = joinTables.get(tableMetadata);
//		if(null != joinTableRuntime){
//			TableObject joinTableObject = generateTableObject(tableMetadata);
//			joinTableRuntime = new JoinTableRuntime(this, joinTableObject);
//		}

		//TableTarget joinTableObject = generateTableObject(tableMetadata);
		
		//TODO 有问题
		TableMetadata preTableMetadata = tableDefinition.getTableMetadata();
		TableInstance table = null;
		if(preTableMetadata.equals(this.tableMetadata)){
			table = this;
		}else{
			table = joinTables.get(preTableMetadata);
			if(null == table){
				throw new Exception("表[" + preTableMetadata.getTableName() 
						+ "]作为被连接(join)的表，必须在连接表[" + tableMetadata.getTableName() 
						+ "]之前被定义！");
			}
		}
		
		TableRuntimeJoin joinTableRuntime = new TableRuntimeJoin(this, table, tableMetadata);
		joinTables.put(tableMetadata, joinTableRuntime);
		return joinTableRuntime;
	}
	
	@Override
	public TableRuntimeJoin join(String name) throws Exception{
		return join(new TableInfo(tableMetadata.getTableInfo(), name));
	}

	@Override
	public TableRuntimeJoin join(TableInfo tableInfo) throws Exception{
		TableMetadata tableMetadata = databaseMetadataSources.loadTableMetadata(databaseMetadataAccessor, tableInfo);
//		JoinTableRuntime joinTableRuntime = joinTables.get(tableMetadata);
//		if(null == joinTableRuntime){
		//TODO 这里使用这个表来定义，但不一定是使用这个表来查询，所以还需要根据一定的规则来解析具体需要查询的表
		//TableTarget joinTableObject = generateTableObject(tableMetadata);
		TableRuntimeJoin joinTableRuntime = new TableRuntimeJoin(this, tableMetadata);
		joinTables.put(tableMetadata, joinTableRuntime);
//		}
		return joinTableRuntime;
	}

	@Override
	public TableRuntimeJoin join(TableInstance table, String name) throws Exception{
		return join(table, new TableInfo(tableMetadata.getTableInfo(), name));
	}

	@Override
	public TableRuntimeJoin join(TableInstance table, TableInfo tableInfo) throws Exception{
		TableMetadata tableMetadata = databaseMetadataSources.loadTableMetadata(databaseMetadataAccessor, tableInfo);
		TableRuntimeJoin joinTableRuntime = new TableRuntimeJoin(this, table, tableMetadata);
		joinTables.put(tableMetadata, joinTableRuntime);
		return joinTableRuntime;
	}
	
	@Override
	public TableInstance find(String alias) {
		for(TableRuntimeJoin t : joinTables.values()){
			if(t.check(alias)) return t;
		}
		return super.find(alias);
	}
	
	public FieldElement tryAddFieldElement(TableInstance tableSource, FieldMetadata fieldMetadata, FieldElementBuilder fieldElementBuilder){
		FieldElement fieldRelation = columns.get(fieldMetadata);
		if(null == fieldRelation){
			fieldRelation = fieldElementBuilder.buildFieldElement(this, tableSource);
			columns.put(fieldMetadata, fieldRelation);
		}
		return fieldRelation;
	}

	/**
	 * <pre>
	 * 结果集里，如果多次引用同一个字段，则查询语句里应该只查询一次；
	 * 然后被不同的取值对象引用，目的是为了优化查询结果集的数据传输。
	 * </pre>
	 */
	@Override
	public FieldElement loadFieldElement(TableInstance tableSource, FieldMetadata fieldMetadata){
		FieldElement fieldRelation = columns.get(fieldMetadata);
		if(null == fieldRelation){
			if(fieldMetadata instanceof FieldElementBuilder){
				
				fieldRelation = ((FieldElementBuilder) fieldMetadata).buildFieldElement(this, tableSource);
				
			}else{
				fieldRelation = new DefaultFieldElement(this, tableSource, fieldMetadata);
			}
			columns.put(fieldMetadata, fieldRelation);
		}
		return fieldRelation;
	}
	
	private class InternalSortFragment implements SortFragment{

		protected SortType sortType;
		private int ordinal;
		private final FieldElement[] fields;
		
		public InternalSortFragment(FieldElement...fields){
			Assert.notEmpty(fields, "排序的字段不能为空！");
			this.fields = fields;
		}

		protected String orderField(FieldElement fieldElement){
			return fieldElement.getAlias();
		}

		public void parse(StringBuilder sql) {
			if(null == sortType)sortType = SortType.NULL;
			
			sql.append(orderField(fields[0])).append(sortType.getValue());
			for(int i = 1; i < fields.length; i++){
				sql.append(',').append(orderField(fields[i])).append(sortType.getValue());
			}
		}
		
		@Override
		public int compareTo(SortFragment o) {
			return ordinal - o.getOrdinal();
		}

		@Override
		public int getOrdinal() {
			return ordinal;
		}

		@Override
		public SortFragment setOrdinal(int ordinal) {
			this.ordinal = ordinal;
			return this;
		}
		
		@Override
		public SortFragment setSortType(SortType sortType) {
			this.sortType = sortType;
			return this;
		}
	}
	
	protected SortFragment _orderBy(TableInstance tableRuntime, String... fieldNames){
		TableMetadata tableMetadata = tableRuntime.getTableMetadata();
		FieldElement[] fieldElements = new FieldElement[fieldNames.length];
		for(int i = 0; i < fieldNames.length; i++){
			FieldMetadata fieldMetadata = tableMetadata.get(fieldNames[i]);
			fieldElements[i] = loadFieldElement(tableRuntime, fieldMetadata);
		}
		return orderBy(new InternalSortFragment(fieldElements));
	}

	@Override
	public SortFragment orderBy(TableInstance tableInstance, String... fieldNames) {
		return _orderBy(tableInstance, fieldNames);
	}
	
	@Override
	public SortFragment orderBy(TableInstance tableInstance, String fieldName) {
		TableMetadata tableMetadata = tableInstance.getTableMetadata();
		FieldMetadata fieldMetadata = tableMetadata.get(fieldName);
		FieldElement fieldElement = loadFieldElement(tableInstance, fieldMetadata);
		return orderBy(new InternalSortFragment(fieldElement));
	}
	
	@Override
	public SortFragment orderBy(TableInstance tableInstance,
			FieldMetadata fieldMetadata, SortType sortType, int ordinal) {
		FieldElement fieldElement = loadFieldElement(tableInstance, fieldMetadata);
		InternalSortFragment ssf = new InternalSortFragment(fieldElement);
		ssf.setSortType(sortType);
		ssf.setOrdinal(ordinal);
		return orderBy(ssf);
	}
	
	@Override
	public SortFragment orderBy(FieldElement fieldElement, SortType sortType, int ordinal) {
		InternalSortFragment ssf = new InternalSortFragment(fieldElement);
		ssf.setSortType(sortType);
		ssf.setOrdinal(ordinal);
		return orderBy(ssf);
	}
	
	private DefaultSelectFragment buildSelectFragment(TableInstance tableSource, FieldMetadata fieldMetadata){
		FieldElement fieldRelation = loadFieldElement(tableSource, fieldMetadata);
		return new DefaultSelectFragment(this, fieldRelation);
	}
	
	protected DefaultSelectFragment[] _field(TableInstance tableSource, String... names) throws Exception{
		int i = 0;
		TableMetadata tableMetadata = tableSource.getTableMetadata();
		DefaultSelectFragment[] nsfs = new DefaultSelectFragment[names.length];
		for(String name : names){
			nsfs[i++] = _field(tableSource, tableMetadata, name);
		}
		return nsfs;
	}

	private DefaultSelectFragment _field(TableInstance tableSource, TableMetadata tableMetadata, String name) throws Exception{
		return _field(tableSource, tableMetadata.get(name));
	}
	
	private DefaultSelectFragment _field(TableInstance tableSource, FieldMetadata fieldMetadata){
		DefaultSelectFragment naturalSelectFragment = buildSelectFragment(tableSource, fieldMetadata);
		fields.add(naturalSelectFragment);
		return naturalSelectFragment;
	}
	
	public void field(SelectFragment selectFragment){
		fields.add(selectFragment);
	}

	public DefaultSelectFragment field(TableInstance tableSource, FieldMetadata fieldMetadata) {
		return _field(tableSource, fieldMetadata);
	}

	@Override
	public DefaultSelectFragment[] field(TableInstance tableInstance, String... names) throws Exception{
		return _field(tableInstance, names);
	}

	@Override
	public ConcatSelectFragment concat(String title) throws Exception{
		return _concat(title);
	}
	
	protected ConcatSelectFragment _concat(String title)throws Exception{
		ConcatSelectFragment concatSelectFragment = new ConcatSelectFragment(this, title);
		columns.put(concatSelectFragment, concatSelectFragment);
		return append(concatSelectFragment);
	}

	@Override
	public ArraySelectFragment array(String title) throws Exception{
		return _array(title);
	}
	
	protected ArraySelectFragment _array(String title) throws Exception{
		return append(new ArraySelectFragment(this, title));
	}
	
	protected <T extends MultipleSelectAppendable> T append(T multipleSelectFragment){
		fields.add(multipleSelectFragment);
		return multipleSelectFragment;
	}

	@Override
	public FormatSelectFragment format(String title, String formatString) throws Exception{
		return _format(title, formatString);
	}
	
	protected FormatSelectFragment _format(String title, String formatString) throws Exception{
		return append(new FormatSelectFragment(this, title, formatString));
	}
	
	@Override
	public QueryFragment simpleQuery(TableInstance tableInstance, String simpleExpression){
		return addQuery(new SimpleQueryFragment(tableInstance, simpleExpression));
	}

	@Override
	public void newQuery(QueryDefinition queryDefine, HttpRequestAccessor paramAccessor, String key) throws Exception{
		TableCriterion tableCriterion = this;
		queryDefine.buildQuery(this, tableCriterion, paramAccessor, key);
	}
	
//	public QueryFragment newQuery(QueryDefinition queryDefine, Opt opt, String[] values) throws Exception {
//		TableCriterion tableCriterion = this;
//		QueryFragment fragment = queryDefine.buildQuery(tableCriterion, opt, values);
//		if(null != fragment){
//			where.add(fragment);
//			return fragment;
//		}
//		return null;
//	}

	// -----------------------------  下面是重新生成暂时表的接口信息
	//这里使用的参数是别名
	@Override
	public FieldMetadata getOrNull(String fieldName) {
		FieldMetadata fieldMetadata = null;
		for(SelectFragment fragment : fields){
			fieldMetadata = fragment.find(fieldName);
			if(null != fieldMetadata) break;
		}
		return fieldMetadata;
	}
	
	@Override
	public FieldMetadata get(String fieldName) {
		FieldMetadata fieldMetadata = null;
		for(SelectFragment fragment : fields){
			fieldMetadata = fragment.find(fieldName);
			if(null != fieldMetadata) return fieldMetadata;
		}
		
		throw new NullPointerException("找不到字段：" + fieldName);
	}

	@Override
	public String getPrimaryKey() {
		//注意：临时表默认是没有主键的
		return tableMetadata.getPrimaryKey();
	}

	@Override
	public TableInfo getTableInfo() {
		if(null == temporaryTable){
			
			/*
			 * 临时表应该是创建在当前的connection连接下面的，所以可以不需要指定catalog之类的限制；
			 * 直接使用临时表名，创建的表也只在当前的connection下有效，但临时表的使用完成后，应该安全
			 * 的删除，因为并不知道这个连接会不会被重用的。
			 */
			temporaryTable = new TableInfo(tableMetadata.getTableInfo(), alias);
		}
		return temporaryTable;
	}
	
	@Override
	public String getTableName() {
		return getTableInfo().getTableName();
	}
	
	private TableInfo temporaryTable;
	

	//tools method
	@Override
	public TableInstance safeLoadSource(TableCriterion tableCriterion, TableDefinitionResource tableDefinition, boolean createIfNeed) throws Exception{
		TableInstance tableSource = null;
		if(tableDefinition instanceof JoinDefinition){
			tableSource = ((JoinDefinition)tableDefinition).loadJoinTable(tableCriterion, createIfNeed);
		}else{
			tableSource = this;
		}
		
		return tableSource;
	}

}
